using System;
using System.Reflection;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection.Emit;
using System.Threading;

using System.Collections;

using System.Diagnostics.SymbolStore;

using Mono;
using Mono.Cecil;
using Mono.Cecil.Metadata;

namespace Pdb.Tools
{
	public class SymUtil
	{
		public static Hashtable GetMethodsSymbolFromType(TypeDefinition td ,ISymbolReader reader)
		{
			Hashtable methods = new Hashtable();

			//AssemblyDefinition ass = AssemblyFactory.GetAssembly(type.Assembly.Location);

			//TypeDefinition td = ass.MainModule.Types[type.FullName];

			foreach(MethodDefinition md in td.Methods)
			{
				if(md.MetadataToken.TokenType == TokenType.Method)
				{
                    ISymbolMethod method = null;
                    try
                    {
                        method = reader.GetMethod(new SymbolToken(0x6000000 + (int)md.MetadataToken.RID));
                    }
                    catch (COMException e)
                    {
                        System.Diagnostics.Debug.WriteLine(e.ToString());
                    }
					methods.Add(md.ToString(), method);
				}
			}

			foreach(MethodDefinition md in td.Constructors)
			{
				if(md.MetadataToken.TokenType == TokenType.Method)
				{
                    ISymbolMethod method = null;
                    try
                    {
                        method = reader.GetMethod(new SymbolToken(0x6000000 + (int)md.MetadataToken.RID));
                    }
                    catch (COMException e)
                    {
                        System.Diagnostics.Debug.WriteLine(e.ToString());
                    }
					methods.Add(md.ToString(), method);
				}
			}
			return methods;
		}

		public static Hashtable GetMethodDefinitionFromType(TypeDefinition td)
		{
			Hashtable methods = new Hashtable();

//			AssemblyDefinition ass = AssemblyFactory.GetAssembly(type.Assembly.Location);
//
//			TypeDefinition td = ass.MainModule.Types[type.FullName];

			foreach(MethodDefinition md in td.Methods)
			{
				if(md.MetadataToken.TokenType == TokenType.Method)
				{
					methods.Add(md.ToString(), md);
				}
			}

			foreach(MethodDefinition md in td.Constructors)
			{
				if(md.MetadataToken.TokenType == TokenType.Method)
				{
					methods.Add(md.ToString(), md);
				}
			}
			return methods;
		}


		public static int GetMethodToken(MethodInfo method)
		{
			Assembly assembly = method.DeclaringType.Assembly;
			AssemblyName assemblyName = new AssemblyName(); 
			assemblyName.Name = assembly.FullName; 

			AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
					
			string n = String.Empty;

			foreach(Module m in assembly.GetModules())
			{
				n = m.Name;
			}

			ModuleBuilder module; 
			module = assemblyBuilder.DefineDynamicModule(n); 

			TypeBuilder typeBuilder = module.DefineType(method.DeclaringType.Name, method.DeclaringType.Attributes);
		
			Type[] parameters = new Type[method.GetParameters().Length];
			for(int i = 0 ; i < method.GetParameters().Length ; i++)
			{
				parameters[i] = method.GetParameters()[i].ParameterType;
			}

			foreach(MethodInfo m in method.DeclaringType.GetMethods())
			{
				MethodBuilder methodbuilder_a = 
					typeBuilder.DefineMethod(
					method.Name,
					method.Attributes,
					method.CallingConvention,
					method.ReturnType,
					parameters);

				int token = methodbuilder_a.GetToken().Token;

				if(m.Name == method.Name)
					return token;
			}
			return 0;
		}

		public static ISymbolReader GetSymbolReaderForFile(string pathModule, string searchPath)
		{
			return SymUtil.GetSymbolReaderForFile(new SymBinder(), pathModule, searchPath);
		}

//		[System.Security.Permissions.SecurityPermission(
//			 System.Security.Permissions.SecurityAction.Demand, 
//			 Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		public static ISymbolReader GetSymbolReaderForFile(SymBinder binder, string pathModule, string searchPath)
		{
			// Guids for imported metadata interfaces.
			Guid dispenserClassID = new Guid(0xe5cb7a31, 0x7512, 0x11d2, 0x89, 0xce, 0x00, 0x80, 0xc7, 0x92, 0xe5, 0xd8); // CLSID_CorMetaDataDispenser
			Guid dispenserIID = new Guid(0x809c652e, 0x7396, 0x11d2, 0x97, 0x71, 0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c); // IID_IMetaDataDispenser
			Guid importerIID = new Guid(0x7dac8207, 0xd3ae, 0x4c75, 0x9b, 0x67, 0x92, 0x80, 0x1a, 0x49, 0x7d, 0x44); // IID_IMetaDataImport

			// First create the Metadata dispenser.
			object objDispenser;
			NativeMethods.CoCreateInstance(ref dispenserClassID, null, 1, ref dispenserIID, out objDispenser);

			// Now open an Importer on the given filename. We'll end up passing this importer straight
			// through to the Binder.
			object objImporter;
			IMetaDataDispenser dispenser = (IMetaDataDispenser)objDispenser;
			dispenser.OpenScope(pathModule, 0, ref importerIID, out objImporter);

			IntPtr importerPtr = IntPtr.Zero;
			//ISymbolReader reader = null;
			try
			{
				// This will manually AddRef the underlying object, so we need to be very careful to Release it.
				importerPtr = Marshal.GetComInterfaceForObject(objImporter, typeof(IMetaDataImport));

				//reader = binder.GetReader(importerPtr.ToInt32(), pathModule, searchPath);

//				if (reader == null)
//					throw new Exception ("no reader");
			}
			catch(Exception e)
			{
				Debug.WriteLine(e.ToString());
			}
			finally
			{
				if (importerPtr != IntPtr.Zero)
				{
					//binder.__dtor();

//					Marshal.Release(importerPtr);
//					Marshal.ReleaseComObject(objDispenser);
//					Marshal.ReleaseComObject(objImporter);
//					Marshal.ReleaseComObject(dispenser);
//
//					Marshal.ReleaseThreadCache();
				}
			}
			return binder.GetReader(importerPtr.ToInt32(), pathModule, searchPath);
		}

//		[System.Security.Permissions.SecurityPermission(
//			 System.Security.Permissions.SecurityAction.Demand, 
//			 Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
		public static ISymbolWriter GetSymbolWriterForFile(string assemblyName, string pdbName)
		{
			Guid dispenserClassID = new Guid(0xe5cb7a31, 0x7512, 0x11d2, 0x89, 0xce, 0x00, 0x80, 0xc7, 0x92, 0xe5, 0xd8); // CLSID_CorMetaDataDispenser
			Guid dispenserIID = new Guid(0x809c652e, 0x7396, 0x11d2, 0x97, 0x71, 0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c); // IID_IMetaDataDispenser
			Guid importerIID = new Guid(0x7dac8207, 0xd3ae, 0x4c75, 0x9b, 0x67, 0x92, 0x80, 0x1a, 0x49, 0x7d, 0x44); // IID_IMetaDataImport
			//Guid emitterIID = new Guid(0x7dac8207, 0xd3ae, 0x4c75, 0x9b, 0x67, 0x92, 0x80, 0x1a, 0x49, 0x7d, 0x45); // IID_IMetaDataExport

			object objDispenser;
			NativeMethods.CoCreateInstance(ref dispenserClassID, null, 1, ref dispenserIID, out objDispenser);

			object objImporter;
			IMetaDataDispenser dispenser = (IMetaDataDispenser)objDispenser;
			dispenser.OpenScope(assemblyName, 0x00000001, ref importerIID, out objImporter);

			IntPtr importerPtr = Marshal.GetComInterfaceForObject(objImporter, typeof(IMetaDataImport));

			SymWriter writer = new SymWriter(false);

			try
			{
				writer.Initialize(importerPtr, pdbName, false);
			}
			catch(Exception e)
			{
				Debug.WriteLine(e.ToString());
			}
			finally
			{
				if (importerPtr != IntPtr.Zero)
				{
					Marshal.Release(importerPtr);
					Marshal.ReleaseComObject(objDispenser);
					Marshal.ReleaseComObject(objImporter);
					Marshal.ReleaseComObject(dispenser);

					Marshal.ReleaseThreadCache();
				}
			}
			return writer;
		}
	}
}
